package errorx

import (
	"gitee.com/zhongguo168a/gocodes/datax/convertx"
	"gitee.com/zhongguo168a/gocodes/datax/mapx"
	"sort"
	"strings"
)

type Error struct {
	error

	ident string
	args  map[string]interface{}
}

func (err *Error) Has(ident string) bool {
	if err.ident == ident {
		return true
	}
	if err.error != nil {
		parent, ok := err.error.(*Error)
		if ok {
			return parent.Has(ident)
		} else {
			return strings.Contains(parent.Error(), ident)
		}
	}
	return false
}

// Error 最终形成字符串：ident?key=value&key2=value2
func (err *Error) Error() (r string) {
	cur := err
	r = createMessage(cur.ident, cur.args)
	for {
		if cur.error == nil {
			break
		}
		next, ok := cur.error.(*Error)
		if !ok {
			r += ": " + cur.error.Error()
			break
		}
		r += ": " + createMessage(next.ident, next.args)
		//
		cur = next
	}
	return
}

// Ident 指定的 ident 用于识别错误的类型。
func (err *Error) Ident() string {
	return err.ident
}

// Args 指定的 args 按照约定格式编写，便于与 ident 分离，更容易的判断错误类型。
// 可通过 mapx 包获取错误时输入的参数
func (err *Error) Args() map[string]interface{} {
	return err.args
}

// New 新建一个错误。
// args只保存第一个元素
func New(ident string, args ...map[string]interface{}) error {
	e := &Error{
		error: nil,
		ident: ident,
	}

	if len(args) > 0 {
		e.args = args[0]
	}

	return e
}

// Wrap 包装一个错误。
// args只保存第一个元素
func Wrap(err error, ident string, args ...map[string]interface{}) error {
	if err == nil {
		return New("wrap err is nil", map[string]interface{}{
			"ident": ident,
		})
	}
	e := &Error{
		error: err,
		ident: ident,
	}

	if len(args) > 0 {
		e.args = args[0]
	}

	return e
}

func createMessage(ident string, args map[string]interface{}) string {
	var argstr = ""
	if args != nil {
		var arr []string
		keys := mapx.Keys(args)
		sort.Strings(keys)
		for _, mkey := range keys {
			mval := args[mkey]
			arr = append(arr, mkey+"="+convertx.AnyToString(mval))
		}
		argstr = "?" + strings.Join(arr, "&")
	}

	return ident + argstr
}

func Has(err error, ident string) bool {
	if err != nil {
		parent, ok := err.(*Error)
		if ok {
			return parent.Has(ident)
		} else {
			return strings.Contains(parent.Error(), ident)
		}
	}
	return false
}

func FormatErrors(errs []error) string {
	var arr []string
	for _, err := range errs {
		arr = append(arr, err.Error())
	}

	return strings.Join(arr, "\n")
}
